// @Date         2021-11-10
// @Author       Aaron Zhang
// @Description
// @FileName      app.go

package command

import (
	"context"
	"errors"
	"fmt"
	"github.com/erikdubbelboer/gspt"
	"github.com/sevlyar/go-daemon"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"
	frameworkHttp "zin/app/http"
	"zin/config"
	"zin/framework"
	"zin/framework/cobra"
	_const "zin/framework/const"
	"zin/framework/contract"
	"zin/framework/provider/cache"
	"zin/framework/provider/id"
	"zin/framework/provider/kernel"
	logger "zin/framework/provider/log"
	"zin/framework/provider/orm"
	"zin/framework/provider/redis"
	"zin/framework/provider/trace"
	"zin/framework/util"
)

//var mode string //Gin的运行模式 debug test release
//var port string //运行的端口，默认8080

// initAppCommand 初始化app命令和其子命令
func initAppCommand() *cobra.Command {
	parseAppFlags()
	appCommand.AddCommand(appStartCommand, appStateCommand, appStopCommand, appRestartCommand)
	return appCommand
}

// AppCommand 是命令行参数第一级为app的命令，它没有实际功能，只是打印帮助文档
var appCommand = &cobra.Command{
	Use:   "app",
	Short: "业务应用控制命令",
	Long:  "业务应用控制命令，其包含业务启动，关闭，重启，查询等功能",
	RunE: func(c *cobra.Command, args []string) error {
		// 打印帮助文档
		c.Help()
		return nil
	},
}

// appStartCommand 启动一个Web服务
var appStartCommand = &cobra.Command{
	Use:   "start",
	Short: "启动一个Web服务",
	RunE: func(c *cobra.Command, args []string) error {
		// 从Command中获取服务容器
		container := c.GetContainer()

		env := util.GetEnvProvider().Env()
		port := config.Conf.App.Port

		if port == "" {
			port = "8080"
		}
		if env == "" {
			env = "dev"
		}

		if !util.InArray(_const.AppEnv, env) {
			log.Fatalln("app_env 只能为 [dev|test|prd] 中的一项")
		}

		log.Printf("app 的运行模式为：%s\n", env)
		log.Printf("app 的运行端口为：%s\n", port)

		if engine, err := frameworkHttp.NewHttpEngine(env); err == nil {
			err := container.Bind(&kernel.ProviderKernel{HttpEngine: engine})
			if err != nil {
				log.Fatalln("绑定容器发生错误", err)
			}
		} else {
			panic(err)
		}

		// 从服务容器中获取kernel的服务实例
		kernelService := container.MustMake(contract.ProviderKeyKernel).(contract.IContractKernel)
		// 从kernel服务实例中获取引擎
		core := kernelService.HttpEngine()

		// 创建一个Server服务
		portN := ":" + strings.Replace(port, ":", "", 1)

		server := &http.Server{
			Handler: core,
			Addr:    portN,
		}

		pidFile := filepath.Join(util.GetAppProvider().RuntimeFolder(), "app.pid")
		logFile := filepath.Join(util.GetAppProvider().LogFolder(), "app.log")
		currentDir := util.GetExecDirectory()

		if appDaemon {
			bgCtx := &daemon.Context{
				PidFileName: pidFile,
				PidFilePerm: 0664,
				LogFileName: logFile,
				LogFilePerm: 0664,
				WorkDir:     currentDir,
				Umask:       027,
				Args:        []string{"", "app", "start", "--daemon=true"},
			}
			// 启动子进程，d不为空表示当前是父进程，d为空表示当前是子进程
			d, err := bgCtx.Reborn()
			defer bgCtx.Release()

			if err != nil {
				return err
			}

			if d != nil {
				// 父进程直接打印启动成功信息，不做任何操作
				fmt.Println("app started, pid : ", d.Pid)
				fmt.Println("logFile : ", logFile)
				return nil
			}

			// 子进程执行真正的app启动操作
			fmt.Println("app started")
			gspt.SetProcTitle("zin app")

			if err := startAppServer(server, container); err != nil {
				fmt.Println(err)
			}
			return nil
		}

		// 非deamon模式，直接执行
		content := strconv.Itoa(os.Getpid())
		fmt.Println("app started, pid : ", content)

		err := ioutil.WriteFile(pidFile, []byte(content), 0644)
		if err != nil {
			return err
		}
		gspt.SetProcTitle("zin app")

		fmt.Println("app serve url:", port)
		if err := startAppServer(server, container); err != nil {
			fmt.Println(err)
		}
		return nil
	},
}

var appStateCommand = &cobra.Command{
	Use:   "state",
	Short: "查看app运行状态",
	RunE: func(cmd *cobra.Command, args []string) error {

		// 获取pid
		pidFile := filepath.Join(util.GetAppProvider().RuntimeFolder(), "app.pid")
		fInfo, _ := os.Stat(pidFile)
		if fInfo == nil {
			fmt.Println("没有app服务存在")
			return nil
		}
		content, err := ioutil.ReadFile(pidFile)
		if err != nil {
			return err
		}

		if content != nil && len(content) > 0 {
			pid, err := strconv.Atoi(string(content))
			if err != nil {
				return err
			}
			if util.CheckProcessExist(pid) {
				fmt.Println("app服务已经启动, pid:", pid)
				return nil
			}
		}

		fmt.Println("没有app服务存在")
		return nil
	},
}

// 停止一个已经启动的app服务
var appStopCommand = &cobra.Command{
	Use:   "stop",
	Short: "停止一个已经启动的app服务",
	RunE: func(c *cobra.Command, args []string) error {
		pidFile := filepath.Join(util.GetAppProvider().RuntimeFolder(), "app.pid")

		content, err := ioutil.ReadFile(pidFile)
		if err != nil {
			return errors.New("open pid file fail")
		}

		if content != nil && len(content) != 0 {
			pid, err := strconv.Atoi(string(content))
			if err != nil {
				return err
			}

			// 发送SIGTERM命令
			if err := syscall.Kill(pid, syscall.SIGTERM); err != nil {
				return errors.New(fmt.Sprintf("结束APP进程失败， Pid:%d", pid))
			}

			if err := ioutil.WriteFile(pidFile, []byte{}, 0644); err != nil {
				return errors.New("clear pid file fail")
			}

			fmt.Println("停止进程:", pid)
		}

		return nil
	},
}

// 重新启动一个app服务
var appRestartCommand = &cobra.Command{
	Use:   "restart",
	Short: "重新启动一个app服务",
	RunE: func(c *cobra.Command, args []string) error {
		pidFile := filepath.Join(util.GetAppProvider().RuntimeFolder(), "app.pid")

		content, err := ioutil.ReadFile(pidFile)
		if err != nil {
			return err
		}

		if content != nil && len(content) != 0 {
			pid, err := strconv.Atoi(string(content))
			if err != nil {
				return err
			}

			if util.CheckProcessExist(pid) {
				// 杀死进程
				if err := syscall.Kill(pid, syscall.SIGTERM); err != nil {
					return err
				}

				// 获取closeWait
				closeWait := 5
				if config.Conf.App.CloseWait > 0 {
					closeWait = config.Conf.App.CloseWait
				}

				// 确认进程已经关闭,每秒检测一次， 最多检测closeWait * 2秒
				for i := 0; i < closeWait*2; i++ {
					if util.CheckProcessExist(pid) == false {
						break
					}
					time.Sleep(1 * time.Second)
				}

				// 如果进程等待了2*closeWait之后还没结束，返回错误，不进行后续的操作
				if util.CheckProcessExist(pid) == true {
					fmt.Println("结束进程失败:"+strconv.Itoa(pid), "请查看原因")
					return errors.New("结束进程失败")
				}

				if err := ioutil.WriteFile(pidFile, []byte{}, 0644); err != nil {
					return err
				}

				fmt.Println("结束进程成功:" + strconv.Itoa(pid))
			}
		}

		// 直接daemon方式启动apps
		appDaemon = true
		return appStartCommand.RunE(c, args)
	},
}

func startAppServer(server *http.Server, container framework.Container) error {
	var err error
	var loadProviders = []framework.ServiceProvider{
		&logger.ProviderLog{},
		&id.ProviderId{},
		&trace.ProviderTrace{},
		&redis.ProviderRedis{},
		&cache.ProviderCache{},
		&orm.ProviderOrm{},
	}
	for _, v := range loadProviders {
		err = container.Bind(v)
		if err != nil {
			log.Fatalln(err)
		}
	}
	// 这个goroutine是启动服务的goroutine
	go func() {
		server.ListenAndServe()
	}()

	// 当前的goroutine等待信号量
	quit := make(chan os.Signal)
	// 监控信号：SIGINT, SIGTERM, SIGQUIT
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	// 这里会阻塞当前goroutine等待信号
	<-quit

	// 调用Server.Shutdown graceful结束
	timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Duration(config.Conf.App.CloseWait)*time.Second)
	defer cancel()

	if err := server.Shutdown(timeoutCtx); err != nil {
		return err
	}

	return nil
}

var appDaemon bool

//解析 start 后面的 flag参数，控制是否后台运行
func parseAppFlags() {
	appStartCommand.Flags().BoolVarP(&appDaemon, "daemon", "d", false, "是否后台运行")
}
